{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction to Promises and PromiseTensors\n",
    "\n",
    "\n",
    "### Context \n",
    "\n",
    "We introduce here Promises and, more specificly, PromiseTensors. The principle is quite similar to promises or futures that you may have encountered in some programming languages as Javascript. As stated in wikipedia, \"they describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is not yet complete.\"\n",
    "\n",
    "In particular, we will explore PromiseTensors which inherit from the Promise class and are used when the object that is waited for is a torch tensor. \n",
    "\n",
    "Let's write some code!\n",
    "\n",
    "Author:\n",
    "- Jason Paumier - GitHub: [@Jasopaum](https://github.com/https://github.com/Jasopaum)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Imports and setup\n",
    "\n",
    "First let's make the classic imports and hooking."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import syft as sy\n",
    "\n",
    "hook = sy.TorchHook(torch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Also one important note: **the local worker should not be a client worker.** *Non client workers can store objects and we need this ability to use promises.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "hook.local_worker.is_client_worker = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Creation and basic methods\n",
    "\n",
    "Let's see how to create a PromiseTensor and use its basic methods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[1., 2.],\n",
       "        [3., 4.]])"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# We create a promise for a FloatTensor\n",
    "a = sy.Promise.FloatTensor(shape=torch.Size((2, 2)))\n",
    "\n",
    "# Now, this promise can be kept as follows\n",
    "ta = torch.tensor([[1., 2.], [3., 4.]])\n",
    "a.keep(ta)\n",
    "\n",
    "# Finally, we can retrieve the value\n",
    "a.value()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[5., 6.],\n",
      "        [7., 8.]])\n",
      "tensor([[8., 7.],\n",
      "        [6., 5.]])\n"
     ]
    }
   ],
   "source": [
    "# We can also keep a promise several times before getting the value\n",
    "a.keep(torch.tensor([[5., 6.], [7., 8.]]))\n",
    "a.keep(torch.tensor([[8., 7.], [6., 5.]]))\n",
    "\n",
    "# The subsequent calls to .value() will give the tensors in the same order as they arrived\n",
    "print(a.value())\n",
    "print(a.value())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Operations with promises\n",
    "\n",
    "We can perform mainstream operations with promises."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "promised_result:  (Wrapper)>[PromiseTensor(me:31223276767) -future-> FloatTensor -blocking-> 0 plans]\n",
      "tensor([[4., 4.],\n",
      "        [4., 4.]])\n",
      "tensor([[6., 6.],\n",
      "        [6., 6.]])\n"
     ]
    }
   ],
   "source": [
    "a = sy.Promise.FloatTensor(shape=torch.Size((2, 2)))\n",
    "b = sy.Promise.FloatTensor(shape=torch.Size((2, 2)))\n",
    "\n",
    "promised_result = a + b\n",
    "# The result of an operation involving promises is a promise itself:\n",
    "print(\"promised_result: \", promised_result)\n",
    "\n",
    "a.keep(torch.tensor([[1., 1.], [1., 1.]]))\n",
    "a.keep(torch.tensor([[2., 2.], [2., 2.]]))\n",
    "b.keep(torch.tensor([[3., 3.], [3., 3.]]))\n",
    "b.keep(torch.tensor([[4., 4.], [4., 4.]]))\n",
    "\n",
    "print(promised_result.value())\n",
    "print(promised_result.value())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Operations between promises and basic torch tensors are also possible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[2., 4.],\n",
      "        [6., 8.]])\n"
     ]
    }
   ],
   "source": [
    "a = sy.Promise.FloatTensor(shape=torch.Size((2, 2)))\n",
    "\n",
    "promised_result = 2 * a\n",
    "\n",
    "a.keep(torch.tensor([[1., 2.], [3., 4.]]))\n",
    "\n",
    "print(promised_result.value())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Example on a remote worker\n",
    "\n",
    "PromiseTensors can also be used between different workers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "bob = sy.VirtualWorker(hook, id=\"bob\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(Wrapper)>[PointerTensor | me:40372024233 -> bob:33749923938]\n",
      "(Wrapper)>[PromiseTensor(bob:33749923938) -future-> FloatTensor -blocking-> 0 plans]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "tensor([[-1., -2.],\n",
       "        [-3., -4.]])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = sy.Promise.FloatTensor(shape=torch.Size((3, 3)))\n",
    "b = sy.Promise.FloatTensor(shape=torch.Size((3, 3)))\n",
    "\n",
    "x = a.send(bob)  # Here, x and y are pointers to PromiseTensors\n",
    "y = b.send(bob)  # located on worker Bob\n",
    "\n",
    "res = x - y  # res is a pointer to a PromiseTensor located on Bob\n",
    "print(res)\n",
    "print(bob._objects[res.id_at_location])\n",
    "\n",
    "x.keep(torch.tensor([[1., 1.], [1., 1.]]))\n",
    "y.keep(torch.tensor([[2., 3.], [4., 5.]]))\n",
    "\n",
    "res.value().get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et voilà! We have seen how to use the powerful tool that are promises with PySyft. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Star PySyft on GitHub\n",
    "\n",
    "The easiest way to help our community is just by starring the repositories! This helps raise awareness of the cool tools we're building.\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Pick our tutorials on GitHub!\n",
    "\n",
    "We made really nice tutorials to get a better understanding of what Federated and Privacy-Preserving Learning should look like and how we are building the bricks for this to happen.\n",
    "\n",
    "- [Checkout the PySyft tutorials](https://github.com/OpenMined/PySyft/tree/master/examples/tutorials)\n",
    "\n",
    "\n",
    "### Join our Slack!\n",
    "\n",
    "The best way to keep up to date on the latest advancements is to join our community! \n",
    "\n",
    "- [Join slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### Join a Code Project!\n",
    "\n",
    "The best way to contribute to our community is to become a code contributor! If you want to start \"one off\" mini-projects, you can go to PySyft GitHub Issues page and search for issues marked `Good First Issue`.\n",
    "\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Donate\n",
    "\n",
    "If you don't have time to contribute to our codebase, but would still like to lend support, you can also become a Backer on our Open Collective. All donations go toward our web hosting and other community expenses such as hackathons and meetups!\n",
    "\n",
    "- [Donate through OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
